//! @file   lc_file.cpp
//! @brief  
//! @author Sayan Chaliha
//! @date   September, 2011
//!
//! 
#include <lc_file.h>
#include <lc_system.h>
#include <lc_exception.h>
#include <lc_thread_manager.h>
#include <lc_scoped_pointer.h>

namespace cyan {
  File::Info::Info() {
  }

  File::Info::Info(const File::Info& info) : statBuffer_(info.statBuffer_) {
  }

  File::Info::Info(const FileStatus& fileStat) : statBuffer_(fileStat) {
  }

  File::Info::~Info() {
  }

  Int File::Info::fileDeviceId() const {
#if defined (HAVE_STRUCT_STAT_ST_DEV) || defined (HAVE_STRUCT__STAT_ST_DEV)
    return this->statBuffer_.st_dev;
#else
    return -1;
#endif /* HAVE_STRUCT_STAT_ST_DEV || HAVE_STRUCT__STAT_ST_DEV */
  }

  Time File::Info::fileAccessTime() const {
#if defined (HAVE_STRUCT_STAT_ST_ATIME) || defined (HAVE_STRUCT__STAT_ST_ATIME)
    return this->statBuffer_.st_atime;
#else
    return -1;
#endif /* HAVE_STRUCT_STAT_ST_ATIME || HAVE_STRUCT__STAT_ST_ATIME */
  }

  Time File::Info::fileModificationTime() const {
#if defined (HAVE_STRUCT_STAT_ST_MTIME) || defined (HAVE_STRUCT__STAT_ST_MTIME)
    return this->statBuffer_.st_mtime;
#else
    return -1;
#endif /* HAVE_STRUCT_STAT_ST_MTIME || HAVE_STRUCT__STAT_ST_MTIME */
  }

  Time File::Info::fileCreationTime() const {
#if defined (HAVE_STRUCT_STAT_ST_CTIME) || defined (HAVE_STRUCT__STAT_ST_CTIME)
    return this->statBuffer_.st_ctime;
#else
    return -1;
#endif /* HAVE_STRUCT_STAT_ST_CTIME || HAVE_STRUCT__STAT_ST_CTIME */
  }

  Offset File::Info::fileSize() const {
#if defined (HAVE_STRUCT_STAT_ST_SIZE) || defined (HAVE_STRUCT__STAT_ST_SIZE)
    return this->statBuffer_.st_size;
#else
    return -1;
#endif /* HAVE_STRUCT_STAT_ST_SIZE || HAVE_STRUCT__STAT_ST_SIZE */
  }

  Int64 File::Info::fileBlockCount() const {
#if defined (HAVE_STRUCT_STAT_ST_BLOCKS) || \
    defined (HAVE_STRUCT__STAT_ST_BLOCKS)
    return this->statBuffer_.st_blocks;
#else
    return -1;
#endif /* HAVE_STRUCT_STAT_ST_BLOCKS || HAVE_STRUCT__STAT_ST_BLOCKS */
  }

  Int File::Info::fileOptimumBlockSize() const {
#if defined (HAVE_STRUCT_STAT_ST_BLKSIZE) || \
    defined (HAVE_STRUCT__STAT_ST_BLKSIZE)
    return this->statBuffer_.st_blksize;
#else
    return -1;
#endif /* HAVE_STRUCT_STAT_ST_BLKSIZE || HAVE_STRUCT__STAT_ST_BLKSIZE */
  }

  File::Type File::Info::fileType() const {
#if defined (HAVE_STRUCT_STAT_ST_MODE) || defined (HAVE_STRUCT__STAT_ST_MODE)
    switch (FILE_STAT_MT_MASK & this->statBuffer_.st_mode) {
      case FILE_STAT_DIR_FLAG:    return TYPE_DIRECTORY;
      case FILE_STAT_REG_FLAG:    return TYPE_REGULAR;
      case FILE_STAT_CHR_FLAG:    return TYPE_CHARACTER_SPECIAL;
      default:                    return TYPE_OTHER;
    }
#else
      return TYPE_OTHER;
#endif /* HAVE_STRUCT_STAT_ST_MODE || HAVE_STRUCT__STAT_ST_MODE */
  }

  Int File::Info::updateInfo(const String& fileName) {
    return ::lstat(fileName, &this->statBuffer_);
  }

  Int File::Info::updateInfo(Int fileDescriptor) {
    return ::fstat(fileDescriptor, &this->statBuffer_);
  }

  File::File() : fileName_(""), flags_(FLAG_DEFAULT), mode_(MODE_DEFAULT),
      fileDescriptor_(-1), isOpen_(false), isTemporary_(false) {
  }

  File::File(const String& fileName, Int flags, Int mode) {
    this->open(fileName, flags, mode);
  }

  File::File(const File& file) {
    this->fileName_ = file.fileName_;
    this->flags_ = file.flags_;
    this->mode_ = file.mode_;
    this->isTemporary_ = file.isTemporary_;
    file.isTemporary_ = false;

    File::testThreadCancellation();

    if (file.isOpen()) {
      this->fileDescriptor_ = ::dup(file.fileDescriptor_);
      if (this->fileDescriptor_ < 0)
        System::throwSystemException();
    } else {
      this->open(this->fileName_, this->flags_, this->mode_);
    }
  }

  File::~File() {
    this->close();
  }

  void File::open(const String& fileName, Int flags, Int mode) {
    File::testThreadCancellation();

    this->close();

    this->fileName_ = fileName;
    this->flags_ = flags;
    this->mode_ = mode;
    this->isTemporary_ = false;

    assertLtOrEq(this->fileName_.size(), System::maximumFileNameLength());

    this->fileDescriptor_ = ::open(this->fileName_, this->flags_,
    this->mode_);

    if (this->fileDescriptor_ < 0) {
      System::throwSystemException();
    } else {
      this->isOpen_ = true;
    }
  }

  void File::close() {
    File::testThreadCancellation();

    if (this->isOpen()) {
      ::close(this->fileDescriptor_);
      this->isOpen_ = false;

      if (this->isTemporary_) {
        try {
          this->remove();
          return;
        } catch (...) {
          // Ignore errors
          return;
        }
      }
    }
  }

  Offset File::seek(Offset offset, Whence whence) {
    File::testThreadCancellation();

    Offset returnCode = ::lseek(this->fileDescriptor_, offset, whence);

    if (returnCode < 0)
      System::throwSystemException();

    return returnCode;
  }

  Offset File::seek(ReservedPosition position) {
    switch (position) {
      case POSITION_BEGIN:    return this->seek(0, WHENCE_SET);
      case POSITION_CURRENT:  return this->seek(0, WHENCE_CURRENT);
      case POSITION_END:      return this->seek(0, WHENCE_END);
      default:                return this->seek(0, WHENCE_CURRENT);
    }
  }

  Offset File::tell() const {
    File::testThreadCancellation();

    Offset returnCode = ::lseek(this->fileDescriptor_, 0, WHENCE_CURRENT);

    if (returnCode < 0)
      System::throwSystemException();

    return returnCode;
  }

  SSize File::read(void* buffer, Size amount) {
    this->testThreadCancellation();

    assertNotNull(buffer);

    if (!buffer)
      Exception::throwException(NullPointerException(TEXT("File::read")));

    SSize returnCode = 0;
    if (amount > 0) {
      returnCode = ::read(this->fileDescriptor_, buffer, amount);
      if (returnCode < 0)
        System::throwSystemException();
    }

    return returnCode;
  }

  SSize File::write(void* buffer, Size amount) {
    this->testThreadCancellation();

    assertNotNull(buffer);

    if (!buffer)
      Exception::throwException(NullPointerException(TEXT("File::write")));

    SSize returnCode = 0;
    if (amount > 0) {
      returnCode = ::write(this->fileDescriptor_, buffer, amount);
      if (returnCode < 0)
        System::throwSystemException();
    }

    return returnCode;
  }

  Bool File::isOpen() const {
    return this->isOpen_;
  }

  File::Info File::info() const {
    if (this->isOpen()) {
      File::testThreadCancellation();
      Info info;

      if (info.updateInfo(this->fileDescriptor_) != 0)
        System::throwSystemException();

      return info;
    } else {
      return File::info(this->fileName_);
    }
  }

  Bool File::access(UInt16 mode) const {
    return File::access(this->fileName_, mode);
  }

  void File::chmod(UInt16 mode) const {
    if (this->isOpen()) {
      Int returnCode = ::fchmod(this->fileDescriptor_, mode);

      if (returnCode < 0)
        System::throwSystemException();
    } else {
      File::chmod(this->fileName_, mode);
    }
  }

  void File::remove() {
    if (this->isOpen())
      this->close();

    File::remove(this->fileName_);
  }

  File File::create(const String& fileName, Bool overwrite, UInt16 mode) {
    const Bool openWritable = mode & (File::MODE_WRITE_USER |
        File::MODE_WRITE_GROUP | File::MODE_WRITE_OTHER);

    UInt16 flags = 0;

    flags |= overwrite ? File::FLAG_CREATE : File::FLAG_CREATE_EXCLUSIVE;
    flags |= openWritable ? File::FLAG_READ_WRITE : File::FLAG_READ;

    Int returnCode = ::open(fileName, flags, mode);

    if (returnCode < 0)
      System::throwSystemException();

    File file;
    file.fileName_ = fileName;
    file.fileDescriptor_ = returnCode;
    file.isOpen_ = true;
    file.mode_ = mode;
    file.flags_ = flags;

    return file;
  }

  void File::remove(const String& fileName) {
    Int returnCode = ::remove(fileName);

    if (returnCode != 0)
      System::throwSystemException();
  }

  File::Info File::info(const String& fileName) {
    File::testThreadCancellation();

    Info tmp;
    Int returnCode = tmp.updateInfo(fileName);

    if (returnCode != 0)
      System::throwSystemException();

    return tmp;
  }

  Bool File::access(const String& fileName, UInt16 mode) {
    File::testThreadCancellation();

    if (::access(fileName, mode) != 0) {
      return false;
    } else {
      return true;
    }
  }

  void File::chmod(const String& fileName, UInt16 mode) {
    File::testThreadCancellation();

    Int returnCode = ::lchmod(fileName, mode);

    if (returnCode != 0)
      System::throwSystemException();
  }

  File File::createTemporary() {
    Char tempFileNamePattern[] = TEMPORARY_FILE_NAME_PATTERN;
    const Char* tempFileName = ::mktemp(tempFileNamePattern);

    if (!tempFileName)
      System::throwSystemException();

    File file(tempFileName, File::FLAG_CREATE_EXCLUSIVE |
        File::FLAG_READ_WRITE);
    file.isTemporary_ = true;

    return file;
  }

  template <>
  File& File::operator <<(const String& string) {
    this->write(const_cast<void*> (reinterpret_cast<const void*>
        (string.toCString())), string.size());
    return *this;
  }

  template <>
  File& File::operator <<(const WString& string) {
    this->write(const_cast<void*> (reinterpret_cast<const void*>
        (string.toCString())), string.size() * sizeof (WString::ValueType));
    return *this;
  }

  template <>
  File& File::operator >>(String& string) {
    const Size size = System::standardBufferSize();
    ScopedArray<Char> buffer(new Char[size]);
    this->read(buffer, size);
    string.assign(buffer);
    delete [] buffer;
    return *this;
  }

  template <>
  File& File::operator >>(WString& string) {
    const Size size = System::standardBufferSize();
    ScopedArray<WChar> buffer(new WChar[size]);
    this->read(buffer, size * sizeof (WChar));
    string.assign(buffer);
    return *this;
  }

  void File::testThreadCancellation() {
    Thread* thread = ThreadManager::getContextualThread();
    if (thread && thread->testCancellation()) {
      Exception::throwException
          (InterruptException(TEXT("Operation cancelled")));
    }
  }
}
